package com.tau.egonetwork.linkedin;

import org.json.JSONArray;
import org.json.JSONObject;

import com.tau.egonetwork.common.Configurations;
import com.tau.egonetwork.common.Log;
import com.tau.egonetwork.exceptions.LinkedinThrottleLimitExceededException;
import com.tau.egonetwork.net.EgoNetworkRequest;
import com.tau.egonetwork.net.IProfileResponse;
import com.tau.egonetwork.net.IProfileResponseProgress;
import com.tau.egonetwork.net.IResponse;
import com.tau.egonetwork.profiles.FriendsList;
import com.tau.egonetwork.profiles.Group;
import com.tau.egonetwork.profiles.GroupsList;
import com.tau.egonetwork.profiles.Profile;
import com.tau.egonetwork.profiles.ProfileFile;
import com.tau.egonetwork.profiles.ProfileFile.SocialNetworks;

public class LinkedinProfileResponse implements IProfileResponse<String> {

	private static final String LINKEDIN_FIELD_ID = "id";
	private static final String LINKEDIN_FIELD_FIRST_NAME = "firstName";
	private static final String LINKEDIN_FIELD_LAST_NAME = "lastName";
	private static final String LINKEDIN_FIELD_LOCATION = "location";

	private static final String LINKEDIN_FIELD_CONNECTIONS = "connections";
	private static final String LINKEDIN_FIELD_NAME = "name";
	private static final String LINKEDIN_FIELD_CONNECTIONS_START = "_start";
	private static final String LINKEDIN_FIELD_CONNECTIONS_COUNT = "_count";
	private static final String LINKEDIN_FIELD_CONNECTIONS_TOTAL = "_total";

	private static final String LINKEDIN_FIELD_GROUP_MEMBERSHIP = "groupMemberships";
	private static final String LINKEDIN_FIELD_GROUP = "group";
	
	private static final String LINKEDIN_FIELD_RELATION = "relationToViewer";
	private static final String LINKEDIN_FIELD_PERSON = "person";
	
	private static final String LINKEDIN_ARRAY_VALUES = "values";
		
	private ILinkedinResponseProgress responseProgress = null;
	private String accessToken = null;
	
	/* Since we do subsequent calls for connections in anonymous methods,
	 * the only way to both transfer the values and be able to update
	 * them and have the change be visible outside of the anonymous
	 * methods is to declare them private in the class, not in the method
	 * containing the anonymous... */
	private int startNextPage = 0;
	private String friendsIDs = "";	
	private int connections_read = 0;
	
	private Profile<String> createBasicProfile(JSONObject json, boolean isMain) {

		String id = json.getString(LINKEDIN_FIELD_ID);
		String fname = json.getString(LINKEDIN_FIELD_FIRST_NAME);
		String lname = json.getString(LINKEDIN_FIELD_LAST_NAME);
		
		Profile<String> profile;
		if (isMain) {
			profile = new LinkedinMainProfile(id, (fname + " " + lname).trim());
		} else {
			profile = new LinkedinFollowProfile(id, (fname + " " + lname).trim());
		}
		
		if (json.has(LINKEDIN_FIELD_LOCATION))
			profile.setLocation(
					json.getJSONObject(LINKEDIN_FIELD_LOCATION)
					.getString(LINKEDIN_FIELD_NAME));
		
		return profile;
	}
	
	@Override
	public Profile<String> parseJSONResponse(String response) throws Exception {
	
		responseProgress.onResponseReceived();
		
		JSONObject json = new JSONObject(response.toString());
				
		LinkedinMainProfile profile =
				(LinkedinMainProfile)createBasicProfile(json, true);
		
		responseProgress.onProfileExtracted(profile.getName());
		
		if (!json.has(LINKEDIN_FIELD_CONNECTIONS))
			return profile;

		final FriendsList<String> flist = profile.createFriendsList();
		
		JSONObject connections = json.getJSONObject(LINKEDIN_FIELD_CONNECTIONS);
		JSONArray values = connections.getJSONArray(LINKEDIN_ARRAY_VALUES);
		int connections_count = values.length();
		connections_read = connections_count;
		
		/* Linkedin is unreliable - we don't always get all contacts, sometimes there
		 * is a not-so-temporary problem and results are inconsistent and don't
		 * return all connections. How can we attempt to find the rest of these
		 * connections? By exploiting Linkedin's inconsistency when it returns
		 * mutual friends that it didn't return as a conncetion / friend... */
		if (connections.has(LINKEDIN_FIELD_CONNECTIONS_TOTAL)) {
			connections_count = connections.getInt(LINKEDIN_FIELD_CONNECTIONS_TOTAL);
		}
		
		responseProgress.onStartReadingFriends(connections_count);
		
		final String[] completefriendsIDs = new String[connections_count];
		
		for (int i = 0; i < connections_read; i++) {
			responseProgress.onReadFriend(i);
			JSONObject contact = values.getJSONObject(i);
			LinkedinFollowProfile lfp =
					(LinkedinFollowProfile)createBasicProfile(contact, false);
			flist.add(lfp);

			completefriendsIDs[i] = lfp.getID();
		}
		
		responseProgress.onReadGroups(0); //size doesn't matter to progress bar
		
		/* create groups list: */
		if (json.has(LINKEDIN_FIELD_GROUP_MEMBERSHIP)) {
			
			JSONObject groupMemberships = json.getJSONObject(LINKEDIN_FIELD_GROUP_MEMBERSHIP);
			
			if (groupMemberships.has(LINKEDIN_ARRAY_VALUES)) {

				JSONArray groups = groupMemberships.getJSONArray(LINKEDIN_ARRAY_VALUES);
				int groupsCount = groups.length();
				
				GroupsList<String> glist = profile.createGroupsList();
				
				for (int i = 0; i < groupsCount; i++) {
					JSONObject group = ((JSONObject)groups.get(i)).getJSONObject(LINKEDIN_FIELD_GROUP);
					glist.add(new Group<String>(group.getString(LINKEDIN_FIELD_NAME)));
				}
			}
		}
		
		int maxContactsPerRequest = Configurations.linkedinMaxContactPerRequest();
		
		responseProgress.onStartFriendRequests((connections_count / maxContactsPerRequest) + 1);
		
		EgoNetworkRequest newRequest = new EgoNetworkRequest();
		
		int completeFriendsIndex = 0;
		int requestNum = 0;
		
		try {
		
			do {
				
				responseProgress.onIssueFriendRequest(++requestNum);
			
				friendsIDs = "";
				startNextPage = 0;
				for (int i = 0; i < maxContactsPerRequest && completeFriendsIndex < connections_read; completeFriendsIndex++, i++) {
					if (i > 0)
						friendsIDs += ",";
					friendsIDs += completefriendsIDs[completeFriendsIndex];
				}
				
				do {
					newRequest.doRequest(LinkedinRequest.firstDegreeConnectionsRequests(friendsIDs, accessToken, startNextPage),
							new IResponse() {
								
								@Override
								public Object parseJSONResponse(String response) {
									
									JSONObject json = new JSONObject(response.toString());
		
									JSONArray followers = json.getJSONArray(LINKEDIN_ARRAY_VALUES);
									int mutualFriendsCount = followers.length();
																	
									boolean startHasBeenSet = false;
									friendsIDs = "";
									
									for (int i = 0; i < mutualFriendsCount; i++) {

										responseProgress.onReadMutualFriends(i);

										JSONObject follower = (JSONObject)followers.get(i);
										String id = follower.getString(LINKEDIN_FIELD_ID);
																	
										LinkedinFollowProfile followerProfile =
												(LinkedinFollowProfile)flist.get(id);

										if (followerProfile == null)
											continue;
										
										JSONObject connections = follower.getJSONObject(LINKEDIN_FIELD_RELATION)
												.getJSONObject(LINKEDIN_FIELD_CONNECTIONS);
										if (!connections.has(LINKEDIN_ARRAY_VALUES))
											continue;
										
										if (connections.has(LINKEDIN_FIELD_CONNECTIONS_START)) {
										
											/* If result has no information about start and end, we got all
											 * the possible data, skip this scope of checking and preparing
											 * for a new request (for the data not yet received) */
		
											int start = connections.getInt(LINKEDIN_FIELD_CONNECTIONS_START);
											int count = connections.getInt(LINKEDIN_FIELD_CONNECTIONS_COUNT);
											int total = connections.getInt(LINKEDIN_FIELD_CONNECTIONS_TOTAL);
											
											if (start + count < total) {
												//there's more mutual connections to this contact, needs another request
												if (!startHasBeenSet) {
													startHasBeenSet = true;
													startNextPage = start + count;
												} else {
													friendsIDs += ",";
												}
												friendsIDs += id;
											}
										}
										
										JSONArray mutuals = connections.getJSONArray(LINKEDIN_ARRAY_VALUES);
										int mutualsCount = mutuals.length();
										
										FriendsList<String> fflist = followerProfile.getFriendsList();
										if (fflist == null)
											fflist = followerProfile.createFriendsList();
										
										for (int j = 0; j < mutualsCount; j++) {
											JSONObject mutual = (JSONObject)mutuals.get(j);
											String mutualID = mutual.getJSONObject(LINKEDIN_FIELD_PERSON)
													.getString(LINKEDIN_FIELD_ID);
											Profile<String> mutualProfile = flist.get(mutualID);
											if (mutualProfile != null) {
												fflist.add(mutualProfile);
											} else {
												// Linkedin inconsistency - mutual friends who was never a
												// friend, add him:
												Profile<String> newContactProfile
													= createBasicProfile(mutual.getJSONObject(LINKEDIN_FIELD_PERSON), false);
												//add to main profile's friends list:
												flist.add(newContactProfile);
												//add as mutual friend:
												fflist.add(newContactProfile);
												//add to IDs array to get all mutual friends:
												completefriendsIDs[connections_read++] = newContactProfile.getID();
											}
										}
									}
									
									return null; //nothing to return in sub method
								}
							}, new LinkedinExceptionHandler());
				} while (!friendsIDs.equals(""));
			} while (completeFriendsIndex < connections_read);
			
		} catch (LinkedinThrottleLimitExceededException e) {
			//proceed as planned, log this but don't do anything out of the ordinary
			Log.writeLineToLog("Throttle limit exception caught in LinkedinProfileResponse class");
		}
		
		responseProgress.onSaveToFile();
		
		ProfileFile.saveToFile(SocialNetworks.Linkedin, profile);
				
		return profile;
	}

	public void setAccessToken(String token) {
		accessToken = token;
	}
	
	@Override
	public void setIProfileProgress(IProfileResponseProgress progress) {
		responseProgress = (ILinkedinResponseProgress)progress;
	}

}
